package dev.ece.suit.heightmap;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;

import org.lwjgl.opengl.GL11;

import com.bulletphysics.collision.shapes.BvhTriangleMeshShape;
import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.collision.shapes.TriangleIndexVertexArray;

import dev.ece.core.cells.model.AbstractModel;
import dev.ece.core.cells.model.ModelCollisionShape;
import dev.ece.core.light.Material;
import dev.ece.core.texture.FBOS;
import dev.ece.core.vertex.EBO;
import dev.ece.core.vertex.VAO;
import dev.ece.core.vertex.VBO;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.tools.BufferBuilder;
import dev.ece.util.tools.Camera;
import dev.ece.util.tools.NormalUtils;

/**
 * 高度图
 * @author 飘渺青衣
 * <b>E-mail：</b>3375758@qq.com
 */
public class HeightMapModel extends AbstractModel<Object> implements ModelCollisionShape {
	
	private int cols;
	
	private int rows;
	
	private int count;
	
	private float[][] heights;
	
	private float scale;
	
	private float texRate;
	
	private VBO verticeVBO;
	
	private VBO uvVBO;
	
	private VBO normalsVBO;
	
	private VAO vaoHandler;
	
	private EBO[] eboHandlers;
	
	private Material material;
	
	public HeightMapModel(BufferedImage image, float scale, float heightScale) {
		this(image, scale, heightScale, 1f / ((image.getWidth() - 1) < (image.getHeight() - 1) ? (image.getWidth() - 1) : (image.getHeight() - 1)));
	}
	
	public HeightMapModel(BufferedImage image, float scale, float heightScale, float texRate) {
		this.scale = scale;
		final int v_cols = image.getWidth();
		final int v_rows = image.getHeight();
		//横向地图块数量
		this.cols = v_cols - 1;
		//纵向地图块数量
		this.rows = v_rows - 1;
		Color color = null;
		this.heights = new float[v_rows][v_cols];
		for(int i=0; i<v_rows; i++) {
			for(int j=0; j<v_cols; j++) {
				color = new Color(image.getRGB(j, i));
				heights[i][j] = (color.getRed() + color.getGreen() + color.getBlue()) * heightScale / 3 / 255; 
			}
		}
		this.texRate = texRate;
		material = new Material();
		material.getDiffuse().set(0.4f, 0.4f, 0.4f);
		material.getSpecular().set(0f, 0f, 0f);
		material.setShininess(128);
		
		setup();
	}
	
	@Override
	public void onInitialize() {
		vaoHandler = new VAO();
		vaoHandler.bind();
		vaoHandler.bindVertexArrayBuffer(verticeVBO, 3);
		vaoHandler.bindVertexArrayBuffer(uvVBO, 2);
		vaoHandler.bindVertexArrayBuffer(normalsVBO, 3);
		vaoHandler.finish();
		
		verticeVBO = null;
		uvVBO = null;
		normalsVBO = null;
	}

	protected void setup() {
		final int v_cols = cols + 1;
		final int v_rows = rows + 1;
		Vector3f[] vertice = createVertice(v_cols, v_rows);
		Vector2f[] uv = createUV(v_cols, v_rows);
		Vector3f[] normals = createNormals(v_cols, v_rows, vertice);
		
		verticeVBO = VBO.newInstance(vertice);
		uvVBO = VBO.newInstance(uv);
		normalsVBO = VBO.newInstance(normals);
		VBO.unbind();
		
		this.count = v_cols * 2;
		int[] indice = new int[this.count];
		if(eboHandlers != null) {
			for(int i=0; i < this.eboHandlers.length; i++) {
				this.eboHandlers[i].destroy();
			}
		}
		eboHandlers = new EBO[rows];
		int index = 0;
		for(int i=0; i<rows; i++) {
			for(int j=0; j<v_cols; j++) {
				index = j * 2;
				indice[index] =  (i * v_cols + j);
				indice[index + 1] = ((i + 1) * v_cols + j);
			}
			eboHandlers[i] = EBO.newInstance(indice);
		}
		EBO.unbind();
	}
	
	public CollisionShape createCollisionShape() {
		final int v_cols = cols + 1;
		final int v_rows = rows + 1;
		Vector3f[] vertice = createVertice(v_cols, v_rows);
		int numTriangles = cols * rows * 2;
		ByteBuffer indexBuffer = BufferBuilder.buildByteBuffer(numTriangles * 3 * 4);
		int triangleStride = 12;
		int numVertices = vertice.length;
		ByteBuffer verticeBuffer = BufferBuilder.buildByteBuffer(numVertices * 3 * 4);
		int vertexStride = 12;
		int maxIndex = 0;
		for(int i=0; i<rows; i++) {
			for(int j=0; j<cols; j++) {
				indexBuffer.putInt(i * v_cols + j);
				indexBuffer.putInt((i + 1) * v_cols + j);
				indexBuffer.putInt(i * v_cols + j + 1);
				indexBuffer.putInt(i * v_cols + j + 1);
				indexBuffer.putInt((i + 1) * v_cols + j);
				indexBuffer.putInt((i + 1) * v_cols + j + 1);
				maxIndex = Math.max(maxIndex, (i + 1) * v_cols + j + 1);
			}
		}
		indexBuffer.rewind();
		for(int i=0; i<vertice.length; i++) {
			vertice[i].store(verticeBuffer);
		}
		verticeBuffer.rewind();
		TriangleIndexVertexArray array = new TriangleIndexVertexArray(numTriangles, indexBuffer, triangleStride, numVertices, verticeBuffer, vertexStride);
		return new BvhTriangleMeshShape(array, false);
	}
	
	protected Vector3f[] createNormals(int v_cols, int v_rows, Vector3f[] vertice) {
		Vector3f[] normals = new Vector3f[vertice.length];
		//计算法线
		int index = 0;
		for(int i=0; i<v_rows; i++) {
			for(int j=0; j<v_cols; j++) {
				//顶点坐标
				index = i * v_cols + j;
				normals[index] = NormalUtils.buildNormal(
						vertice[index], 
						i > 0 ? vertice[(i - 1) * v_cols + j] : null, 
						j > 0 ? vertice[index - 1] : null, 
						i + 1 < v_rows ? vertice[(i + 1) * v_cols + j] : null,
						j + 1 < v_cols ? vertice[index + 1] : null, null);
			}
		}
		return normals;
	}

	protected Vector2f[] createUV(int v_cols, int v_rows) {
		Vector2f[] uv = new Vector2f[v_cols * v_rows];
		int index = 0;
		for(int i=0; i<v_rows; i++) {
			for(int j=0; j<v_cols; j++) {
				//顶点坐标
				index = i * v_cols + j;
				uv[index] = new Vector2f(j * texRate, i * texRate);
			}
		}
		return uv;
	}
	
	protected Vector3f[] createVertice(int v_cols, int v_rows) {
		Vector3f[] vertice = new Vector3f[v_cols * v_rows];
		int index = 0;
		for(int i=0; i<v_rows; i++) {
			for(int j=0; j<v_cols; j++) {
				//顶点坐标
				index = i * v_cols + j;
				vertice[index] = new Vector3f(j * scale, this.heights[i][j], i  * scale);
			}
		}
		return vertice;
	}
	
	@Override
	public void onRender(Camera camera, Object value) {
		//绘制
		vaoHandler.bind();
		for(EBO ebo:this.eboHandlers) {
			ebo.bind();
			ebo.glDrawElements(GL11.GL_TRIANGLE_STRIP);
		}
	}
	
	@Override
	public void onRenderAlpha(FBOS defer, Camera camera, Object value) {}

	@Override
	public void onRenderDepthMap(Camera camera, Object value) {
		this.onRender(camera, value);
	}
	
	/**
	 * 根据坐标获得高度
	 * @param x
	 * @param z
	 * @return
	 */
	public float getHeight(final float x, final float z) {
		int v_cols = (int) (x / this.scale);
		int v_rows = (int) (z / this.scale);
		int cols = this.cols + 1;
		int rows = this.rows + 1;
		//在地图顶点范围内
		if(v_cols >= 0 && v_rows >= 0 && v_cols < cols && v_rows < rows) {
			float height = this.heights[v_rows][v_cols];
			float m_width = x - v_cols * this.scale;
			float m_height = z - v_rows * this.scale;
			
			float lt = height;
			float rt = height;
			float lb = height;
			float rb = height;
			
			float w_alpha = m_width / this.scale;
			float h_alpha = m_height / this.scale;
			
			if(v_cols < this.cols) {
				rt = this.heights[v_rows][v_cols + 1];
				if(v_rows < this.rows) {
					rb = this.heights[v_rows + 1][v_cols + 1];
				}
			}
			if(v_rows < this.rows) {
				lb = this.heights[v_rows + 1][v_cols];
			}
			
			float north = (rt - lt) * w_alpha + lt;
			float south = (rb - lb) * w_alpha + lb;
			float west = (lb - lt) * h_alpha + lt;
			float east = (rb - rt) * h_alpha + rt;
			
			return Math.max((south - north) * h_alpha + north, (east - west) * w_alpha + west);
		}
		return 0;
	}
	
	@Override
	public void destroy() {
		this.vaoHandler.destroy();
		this.vaoHandler = null;
		for(int i=0; i < this.eboHandlers.length; i++) {
			this.eboHandlers[i].destroy();
		}
		this.eboHandlers = null;
		heights = null;
	}
	
	public int getCols() {
		return cols;
	}

	public int getRows() {
		return rows;
	}

	public float[][] getHeights() {
		return heights;
	}

	public float getScale() {
		return scale;
	}
	
	public Material getMaterial() {
		return material;
	}
	
}
